package com.example.doc_searcher.searcher;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2025-01-28
 * Time: 12:58
 */
//分析器
public class Parser {
    //通过index来save
    private Index index = new Index();
    //指定一个加载文档的路径
    private static final String INPUT_PATH = "D:/doc_searcher_index/docs/api/";

    private AtomicLong t1 = new AtomicLong(0);//线程安全，内部基于原子操作实现good
    private AtomicLong t2 = new AtomicLong(0);


    public void run() throws IOException, InterruptedException {
        long beg = System.currentTimeMillis();
        //整个Parser类的入口
        //1：根据上面的路径，把该路径下的所有html文档枚举出来，推荐使用递归的方式
        ArrayList<File> fileList = new ArrayList<File>();
        enumFile(INPUT_PATH, fileList);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕，消耗时间为：" + (endEnumFile - beg) + "ms");

        //2：打开枚举出来的文件，读取文件的内容，解析出来，构建索引
        for (File f : fileList) {
            System.out.println("开始解析" + f.getAbsolutePath());
            parseHTML(f);
        }
        long endFor = System.currentTimeMillis();
        System.out.println("遍历文件完毕！消耗时间为：" + (endFor - endEnumFile) + "ms");

        //3：把在内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("单线程索引制作完毕！消耗总时间为：" + (end - beg) + "ms");
    }

    public void runByThread() throws InterruptedException {
        long beg = System.currentTimeMillis();
        System.out.println("制作索引开始！");

        //1：枚举所有文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH, files);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕，消耗时间为：" + (endEnumFile - beg) + "ms");

        //2：循环遍历文件，多线程制作索引
        CountDownLatch latch = new CountDownLatch(files.size());//计数锁存器
        ExecutorService executorService = Executors.newFixedThreadPool(4);//线程池
        for (File f : files) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("开始解析" + f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();
                }
            }); //解析每一个html文件
        }
        //await方法会阻塞，直到所有选手都调用contDown撞线之后，才能阻塞结束
        latch.await();
        //手动干掉非守护线程
        executorService.shutdown();
        long endFor = System.currentTimeMillis();
        System.out.println("遍历文件完毕！消耗时间为：" + (endFor - endEnumFile) + "ms");

        //3：把在内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("多线程下索引制作完毕！消耗总时间为：" + (end - beg) + "ms");
        System.out.println("t1:" + t1 + ", t2:" + t2);
    }

    public void parseHTML(File f) {
        //1：TODO 解析出HTML的标题
        String title = parseTitle(f);
        //2：TODO 解析出HTML的URL
        long beg = System.nanoTime();//纳秒计时
        String url = parseUrl(f);
        long mid = System.nanoTime();
        //3：TODO 解析出HTML对应的正文（有了正文才有后续的描述）
//        String content = parseContent(f);
        String content = parseContentByRegex(f);
        long end = System.nanoTime();//用累加，sout的话本身也会耗时很多
        //4：把解析出来的信息加入到索引当中
        index.addDoc(title, url, content);
        t1.addAndGet(mid - beg);
        t2.addAndGet(end - mid);
    }


    public String parseContent(File f) {
        //
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024 * 1024)) {//缓冲区设置为1M,默认的为8192字节太小
//            FileReader fileReader = new FileReader(f);//这里是从硬盘读，我们改成提前读好，之后从内存中读效率会更高
            //是否拷贝的开关
            boolean isCopy = true;
            //用StringBuilder来保存结果，
            StringBuilder content = new StringBuilder();
            while (true) {
//                int ret = fileReader.read();//读取文件，一个字符一个字符的读，不是字符返回-1；
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;//读完了
                }
                //是字符执行以下逻辑
                char c = (char) ret;
                if (isCopy) {
                    if (c == '<') {
                        isCopy = false;
                        continue;
                    }
                    if (c == '\n' || c == '\r') {//换行的两种方式注意回车键
                        c = ' ';//换成空格
                    }
                    content.append(c);//其它字符直接进行拷贝到StringBuilder中
                } else {
                    //直到遇见'>'这个字符我们才打开拷贝的开关
                    if (c == '>') {
                        isCopy = true;
                    }
                }
            }
//            fileReader.close();
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String readFile(File f){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))){//从内存中读
            StringBuilder content = new StringBuilder();
            while(true){
                int ret = bufferedReader.read();
                if(ret == -1){
                    break;
                }
                char c = (char)ret;
                if(c == '\n' || c == '\r'){
                    c = ' ';
                }
                content.append(c);
            }
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
    public String parseContentByRegex(File f){
        //1：先把整个文件读到String里面
        String content = readFile(f);
        //2:替换掉script 标签
        content = content.replaceAll("<script.*?>(.*?)</script>","");//匹配非换行字符若干次，最短结果（非贪婪）
        //3:替换掉普通的html标签
        content = content.replaceAll("<.*?>"," ");//2，3顺序不能换，不然先把scrip标签整没了，那script标签中的内容咋整？
        //4：合并多个空格为1个空格
        content = content.replaceAll("\\s+"," ");
        return content;
    }


    public String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/en/java/javase/17/docs/api/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());//长度作为起始下标，一直截取到末尾
        return part1 + part2;
    }

    private String parseTitle(File f) {
        String name = f.getName();
        return name.substring(0, name.length() - ".html".length());//左闭右开区间
    }


    public void enumFile(String inputPath, ArrayList<File> fileList) {
        File rootPath = new File(inputPath);//实例化一个该指定路径的目录，用来操作该目录下的文件
        //listFiles方法获取到该api目录中所有的（目录/文件）
        File[] files = rootPath.listFiles();
        for (File f : files) {
//            System.out.println(f);//这些文件不全是我们需要的html文件，可能还是个文件夹，这里就直接递归进去
            if (f.isDirectory()) {
                enumFile(f.getAbsolutePath(), fileList);//是目录就递归,始终用List集合是同一个
            } else if (f.getAbsolutePath().endsWith(".html")) {
                fileList.add(f);//是html文件就把它添加进去//就排除掉了大概200个文件
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Parser parser = new Parser();
//        parser.run();
        parser.runByThread();//制作索引
    }

}



















